/*
 * Package il.ac.biu.cs.grossmm.test.SimpleProtocol
 * File NioServer.java
 * 
 * This is a part of presence service framework API. 
 * See javadoc for more information.
 *  
 * Copyright (C) 2006 Grossmann Mark
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package il.ac.biu.cs.grossmm.foo.protocol;


import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;


public abstract class NioServer implements Runnable {
	long sessionCounter = 0;
	
	ServerSocketChannel ssc;
	ServerSocket ss;
	Selector selector;
	SelectionKey ssKey;
	
	Thread thread;
	
	ByteBuffer inputByteBuffer;
	
	CharsetDecoder decoder;
	
	private static NioServer defaultServer;
	
	HashMap<String, Session> sessions
		= new HashMap<String, Session>();

	public NioServer()
	{
		defaultServer = this;
	}
	
	public static DirectSession directConnect(DirectClient client) {
		return DirectSession.directConnect(defaultServer, client);
	}

	protected void startListening(int port) throws IOException {
		selector = Selector.open();
		ssc = ServerSocketChannel.open();
		ssc.configureBlocking(false);
		
		Charset charset = Charset.forName("utf-8");
		decoder = charset.newDecoder();
		inputByteBuffer = ByteBuffer.allocate(4);
		
		InetSocketAddress address = new InetSocketAddress(port);
		ss = ssc.socket();
		ss.bind( address );
		ssc.register(selector, SelectionKey.OP_ACCEPT);
		
		thread = new Thread(this, "NioServer Listener");
		thread.start();
	}
	

	protected void stopListening() {
		Set<SelectionKey> keys = selector.keys();
		
		thread.interrupt();
		selector.wakeup();
		
		try {
			thread.join();
		} catch (InterruptedException e) {
			return;
		}
		
		for( SelectionKey key : keys ) {
			if( key.isReadable() || key.isWritable() ) {
				Session session = (Session) key.attachment();
				if( session == null )
					continue;
				
				session.terminate();
			}  else {
				try {
					key.channel().close();
				} catch (IOException e) {
					// ignore
				}
			}
			
			try {
				selector.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	public void process(Session session, String string) throws IOException
	{
		// System.out.println("Processing "+string);
		
		Message m = Message.parse(string);
		
		messageReceived(session, m);
	}


	/**
	 * @param session
	 * @param m
	 */
	protected void messageReceived(Session session, Message m)
	{
		if( m instanceof Request ) {
			if( m instanceof PingRequest ) {
				session.send(((PingRequest) m).okResponse());
			} else
				requestReceived(session, (Request)m);
		} else
			responseReceived(session, (Response)m);
	}




	public void run()
	{
		while( !thread.isInterrupted() ) {
			int num;
			try {
				num = selector.select();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return;
			}
			
			
			Set<SelectionKey> selectedKeys = selector.selectedKeys();
			Iterator<SelectionKey> it = selectedKeys.iterator();
			
			while( it.hasNext() ) {
				SelectionKey key = it.next();
				//selectedKeys.remove(key);
				it.remove();
				
//				System.out.println("Processing selection key read="
//					+ key.isReadable() + " write=" + key.isWritable()
//					+ " accept=" + key.isAcceptable());
				
				if(! key.channel().isOpen() ) {
					key.cancel();
					Session session = (Session) key.attachment();
					sessions.remove(session.sessionId);
					continue;
				}
				
				if( key.isAcceptable() )
					acceptConnections(key);
				if( key.isReadable() )
					acceptMessage(key);
				if( key.isWritable() ) {
					Session session = (Session) key.attachment();
					session.write();
				}
					
			}
		}
		
			
	}
	
	

	private void acceptMessage(SelectionKey key)
	{
		Session cs = (Session) key.attachment();
		try {
			cs.read();
		} catch (IOException e) {
			e.printStackTrace();
			try {
				cs.channel.close();
			} catch (IOException e1) {
			}
		}
	}

	private void acceptConnections(SelectionKey key)
	{

		System.out.println("connect");
		ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
		SocketChannel sc;
		
		try {
			 sc = ssc.accept();
			 sc.configureBlocking(false);
			 
			 SelectionKey readKey = 
				 sc.register(this.selector, SelectionKey.OP_READ);
			 
			 Session session= new Session(this, sc);
			 readKey.attach(session);
			 sessions.put(session.sessionId, session);
		} catch (IOException e) {
			e.printStackTrace();
			return;
		}
	}
	
	
	protected void terminating(Session session)
	{
	}

	/**
	 * Default implementation
	 */
	private void responseReceived(Session session, Response response)
	{
		if( response.code != 200 )
			session.terminate();
	}
	
	protected abstract void requestReceived(Session session, Request request);

}
